/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.wikimark;

import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.URL;
import java.util.Date;
import java.util.Properties;

import javax.servlet.Servlet;

import org.apache.wikimark.http.HttpConnectivity;
import org.apache.wikimark.http.LocalConnectivity;

final class ArgConfig implements Config, TestEventListener {
	private int[] threads;
	private int[][] density = { { 1 << 10, 1 }, { 2 << 10, 3 }, { 4 << 10, 4 },
			{ 8 << 10, 5 }, { 16 << 10, 4 }, { 32 << 10, 2 }, { 64 << 10, 1 } };
	private int pagesMult = 1;
	private int requests = 5;
	private Driver driver;
	private URL url;
	private Connectivity connectivity;
	private Class<? extends Servlet> servlet;
	private PrintWriter statOut;
	private int curThreads;
	private int curRequest;
	private long prevOutputLength;
	private Writer logger;
	private Statistics statistics;

	ArgConfig(final String[] args) throws Exception {
		try {
			processProperties();
			processArgs(args);
			multiplyDensity();
			initThreadsArray();
			createConnectivity();
		} catch (final IllegalArgumentException e) {
			System.err.println(e);
			printUsage();
			if (connectivity != null) {
				connectivity.close();
			}
			throw e;
		}
	}

	private void initThreadsArray() {
		if (threads == null) {
			threads = new int[Runtime.getRuntime().availableProcessors() * 2];
			for (int i = 0; i < threads.length; i++) {
				threads[i] = i + 1;
			}
		}
	}

	private void createConnectivity() throws IOException {
		if (connectivity == null) {
			String protocol = url.getProtocol();
			if ("http".equals(protocol)) {
				connectivity = new HttpConnectivity();
			} else if ("file".equals(protocol)) {
				connectivity = new LocalConnectivity(servlet);
			} else {
				throw new IllegalArgumentException("unknown protocol "
						+ protocol);
			}
		}
		connectivity.setBaseURL(url);
		if (driver == null) {
			driver = DefaultDriverSet
					.findDriver(connectivity.requestPage(null));
		}
	}

	private void multiplyDensity() {
		for (int[] entry : density) {
			entry[1] *= pagesMult;
		}
	}

	private static void printUsage() {
		System.err
				.println("Usage: java org.apache.wikimark.WikiMark"
						+ " [-t threadNum] [-s sizeInKB] [-p pageNumPerThread]"
						+ " [-r requestsPerPage] [-d driverClassName]"
						+ " [-c ConnectivityClassName] [-servlet ServletClassName]"
						+ " [-f statistics_file | System.out] [-log_output] mainPageURL");
		System.err.println("\tthe servlet is used for connection, if "
				+ "mainPageURL is local file and -c is not supplied");
	}

	@SuppressWarnings("unchecked")
	private void processProperties()
			throws InstantiationException, IllegalAccessException,
			ClassNotFoundException, IOException {

		Properties prop = System.getProperties();
		
		String threadsVal = prop.getProperty("wikimark.threads");		
		if (threadsVal != null) {
			threads = new int[] { Integer.parseInt(threadsVal) };
		}
		
		String sizeVal = prop.getProperty("wikimark.pageSize");		
		if (sizeVal != null) {
			density = new int[][] { { Integer.parseInt(sizeVal) << 10, 1 } };
		}

		String multVal = prop.getProperty("wikimark.pages");				
		if (multVal != null) {
			pagesMult = Integer.parseInt(multVal);
		}
		
		String requestsVal = prop.getProperty("wikimark.requests");				
		if (requestsVal != null) {
			requests = Integer.parseInt(requestsVal);
		}

		String driverVal = prop.getProperty("wikimark.driver");				
		if (driverVal != null) {
			driver = (Driver) Class.forName(driverVal).newInstance();
		}

		String connectivityVal = prop.getProperty("wikimark.connectivity");				
		if (connectivityVal != null) {
			connectivity = (Connectivity) Class.forName(connectivityVal)
				.newInstance();
		}

		String statVal = prop.getProperty("wikimark.statistics");				
		if (statVal != null) {
			Writer w = statVal.equals("System.out") ? new OutputStreamWriter(
					System.out)	: new FileWriter(statVal);
			statOut = new PrintWriter(w);
		}
		
		String servletVal = prop.getProperty("wikimark.servlet");				
		if (servletVal != null) {
			servlet = ((Class) Class.forName(servletVal))
				.asSubclass(Servlet.class);
		}

		String logOutputVal = prop.getProperty("wikimark.logOutput");
		if (Boolean.parseBoolean(logOutputVal)) {
			logger = new OutputStreamWriter(System.out);
		}
	}

	@SuppressWarnings("unchecked")
	private void processArgs(final String[] args)
			throws InstantiationException, IllegalAccessException,
			ClassNotFoundException, IOException {
		for (int i = 0; i < args.length;) {
			String arg = args[i++];
			if ("-t".equals(arg)) {
				threads = new int[] { Integer.parseInt(args[i++]) };
			} else if ("-s".equals(arg)) {
				density = new int[][] { { Integer.parseInt(args[i++]) << 10, 1 } };
			} else if ("-p".equals(arg)) {
				pagesMult = Integer.parseInt(args[i++]);
			} else if ("-r".equals(arg)) {
				requests = Integer.parseInt(args[i++]);
			} else if ("-d".equals(arg)) {
				driver = (Driver) Class.forName(args[i++]).newInstance();
			} else if ("-c".equals(arg)) {
				connectivity = (Connectivity) Class.forName(args[i++])
						.newInstance();
			} else if ("-f".equals(arg)) {
				String fileName = args[i++];
				Writer w = fileName.equals("System.out") ? new OutputStreamWriter(
						System.out)
						: new FileWriter(fileName);

				statOut = new PrintWriter(w);
			} else if ("-servlet".equals(arg)) {
				servlet = ((Class) Class.forName(args[i++]))
						.asSubclass(Servlet.class);
			} else if ("-log_output".equals(arg)) {
				logger = new OutputStreamWriter(System.out);
			} else {
				url = new URL(arg);
				if (i != args.length) {
					throw new IllegalArgumentException("too many arguments");
				}
			}
		}
		if (url == null) {
			throw new IllegalArgumentException("no URL");
		}
	}

	public Driver getDriver() {
		return driver;
	}

	public int getRequestPerPageCount() {
		return requests;
	}

	public URL getBaseUrl() {
		return url;
	}

	public Connectivity getConnectivity() {
		return connectivity;
	}

	public int[][] getPageSizeDensity() {
		return density;
	}

	public int[] getThreadCounts() {
		return threads;
	}

	public TestEventListener getTestEventListener() {
		return this;
	}

	public void onStart() {
		System.out.println("Mark info: benchmark started: " + new Date());
		if (statOut != null) {
			statistics = new Statistics(threads, requests);
		}
	}

	public void onFinish() {
		if (statOut != null) {
			statistics.doCSVOut(statOut);
		}
		System.out.println("Mark info: benchmark finished: " + new Date());
	}

	public void onTestSetup(int curThreads) {
		this.curThreads = curThreads;
		System.out.print("\nTest started: threads = " + curThreads);
		System.out.println();
	}

	public void onTestStart(int curRequest) {
		this.curRequest = curRequest;
		System.out.print("request = " + (curRequest + 1));
	}

	public void onTestFinish(TestResult res) {
		checkResult(res);

		final int throughput = (int) (((res.outputLength * 1000) >> 10) / Math
				.max(1, res.timeElapsed));

		System.out.println(" throughput = " + throughput);
		if (statOut != null) {
			statistics.add(curThreads, curRequest, throughput);
		}
	}

	private void checkResult(TestResult res) {
		int variation = getNumPages() * getRequestPerPageCount()
			* getDriver().getPageSizeVariation() 
			* getThreadCounts()[getThreadCounts().length - 1];
		
		if (curRequest > 0 
				&& Math.abs(prevOutputLength - res.outputLength) > variation) {
			
			throw new RuntimeException(
					"total request output sizes aren't equal");
		}
		prevOutputLength = res.outputLength;
	}

	private int getNumPages() {
		int sum = 0;
		for (int[] row : getPageSizeDensity()) {
			sum += row[1];
		}
		return sum;
	}

	public void onTestTeardown() {
	}

	public void close() throws IOException {
		connectivity.close();
	}

	public Writer getOutputLogger() {
		return logger;
	}
}